// -----------------------------------------------------------------------
//  <copyright file="AspNetCoreModule.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2022 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://www.dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2022-11-27 1:50</last-date>
// -----------------------------------------------------------------------


using System.Security.Claims;

using DaprPlus.Json;
using System.Text.Json;

using DaprPlus.AspNetCore.Extensions;
using DaprPlus.AspNetCore.Mvc.Filters;
using DaprPlus.AspNetCore.SignalR;
using DaprPlus.Properties;

using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;

using OSharp.Localized;


namespace DaprPlus.AspNetCore;

/// <summary>
/// AspNetCore模块
/// </summary>
[LocalizedDescription("String_Module_AspNetCore", typeof(ResDaprPlus))]
[DependsOnModules(typeof(CoreModule))]
public class AspNetCoreModule : AspModuleBase
{
    private ICorsInitializer? _corsInitializer;

    /// <summary>
    /// 获取 模块级别，级别越小越先启动
    /// </summary>
    public override ModuleLevel Level => ModuleLevel.Framework;

    /// <summary>
    /// 获取 模块启动顺序，模块启动的顺序按级别启动，同一级别内部再按此顺序启动
    /// 级别默认为0，表示无依赖，需要在同级别有依赖顺序的时候，再重写为>0的顺序值
    /// </summary>
    public override int Order => 1;

    /// <summary>
    /// 将模块服务添加到依赖注入服务容器中
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <returns></returns>
    public override IServiceCollection AddServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();

        //注入当前用户，替换Thread.CurrentPrincipal的作用
        services.AddTransient<IPrincipal>(provider =>
        {
            var accessor = provider.GetService<IHttpContextAccessor>();
            return accessor?.HttpContext?.User ?? new ClaimsPrincipal();
        });

        services.AddSingleton<IApiResultExceptionHandler, DefaultExceptionHandler>();
        ;
        services.Replace<ICancellationTokenProvider, HttpContextCancellationTokenProvider>(ServiceLifetime.Singleton);

        AddCors(services);

        AddAspNetCore(services);

        return base.AddServices(services);
    }

    protected virtual IServiceCollection AddCors(IServiceCollection services)
    {
        services.Configure<CorsOptions>(sectionName: CorsOptions.Position);
        _corsInitializer = services.GetOrAddSingletonInstance<ICorsInitializer>(() => new DefaultCorsInitializer());
        _corsInitializer.AddCors(services);

        return services;
    }

    protected virtual IServiceCollection AddAspNetCore(IServiceCollection services)
    {
        services.Configure<AspNetCoreOptions>(AspNetCoreOptions.Position);
        var options = services.GetOptions<AspNetCoreOptions>(AspNetCoreOptions.Position);

        var mvcBuilder = services.AddControllers();
        if (!options.IsApiOnly)
        {
            if (options.IsMvc)
            {
                mvcBuilder = services.AddControllersWithViews();
            }
            else if (options.IsRazorPage)
            {
                mvcBuilder = services.AddRazorPages();
            }
        }

        mvcBuilder = mvcBuilder.AddControllersAsServices();
        mvcBuilder.AddJsonOptions(opts =>
        {
            if (options.IsLowercaseJsonProperty)
            {
                opts.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            }
            else
            {
                opts.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
                opts.JsonSerializerOptions.PropertyNamingPolicy = null;
            }

            if (options.IsLongToStringConvert)
            {
                opts.JsonSerializerOptions.Converters.Add(new LongToStringTextConverter());
            }
        });
        MvcBuilderAction(services, mvcBuilder, options);

        if (options.IsSignalR)
        {
            services.TryAddSingleton<IUserIdProvider, UserNameUserIdProvider>();
            services.TryAddSingleton<IConnectionUserCache, ConnectionUserCache>();

            var signalRBuilder = services.AddSignalR();
            signalRBuilder.AddJsonProtocol(opts =>
            {
                if (options.IsLowercaseJsonProperty)
                {
                    opts.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
                    opts.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                }
                else
                {
                    opts.PayloadSerializerOptions.PropertyNameCaseInsensitive = false;
                    opts.PayloadSerializerOptions.PropertyNamingPolicy = null;
                }

                if (options.IsLongToStringConvert)
                {
                    opts.PayloadSerializerOptions.Converters.Add(new LongToStringTextConverter());
                }
            });
            SignalRBuilderAction(services, signalRBuilder, options);
        }

        services.AddRouting(opts => opts.LowercaseUrls = options.IsLowercaseUrls);

        return services;
    }

    protected virtual void MvcBuilderAction(IServiceCollection services, IMvcBuilder mvcBuilder,
        AspNetCoreOptions options)
    { }

    protected virtual void SignalRBuilderAction(IServiceCollection services, ISignalRServerBuilder builder,
        AspNetCoreOptions options)
    { }

    /// <summary>
    /// 应用AspNetCore的服务业务
    /// </summary>
    /// <param name="app">Web应用程序</param>
    public override Task UseModule(WebApplication app)
    {
        var options = app.Services.GetService<IOptions<AspNetCoreOptions>>()?.Value;
        if (options?.IsApiOnly == false)
        {
            app.UseStaticFiles();
            app.MapStaticAssets();
            if (options.IsRazorPage)
            {
                app.MapRazorPages();
            }
        }
        app.UseRouting();
        _corsInitializer?.UseCors(app);
        app.MapControllers();
        app.MapControllerWithAreaRoute();
        return base.UseModule(app);
    }
}
